package escalonamento;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import model.Processo;

/**
 *
 * @author João Gonçalves
 * @author Eventon Silva
 */
public class EscalonamentoRR {

    // Tempo decorrido durante o processamento
    int tempoDecorrido;
    // Tempo médio de execução
    double tempoMedioExecucao;
    // Quantidade de trocas de contextos
    int trocaContexto;
    // Tempo médio de espera de um processo
    double tempoMedioEspera;
    // Quantum do escalonamento
    int quantum;
    // Quantidade de processos
    int quantProcessos;
    // Processo em execução
    Processo processoExecucao;
    // Lista com os processos em execução e o com os dados recolhidos, respectivamente.
    Queue<Processo> listaProcesso;
    Queue<Processo> listaProcessoDados;
    // Construção da String com os dados do processamento
    StringBuilder dadosProcessamento;

    private void novoProcessoExecucao() {
        if ((!listaProcesso.isEmpty()) && (listaProcesso.peek().getStatus() == 0)) {

            processoExecucao = listaProcesso.peek();
            processoExecucao.setStatus(1);
            quantum = 2;
            processoExecucao.tempo();
            quantum--;
            trocaContexto++; // Nova troca de contexto
        }
    }

    /**
     * Método responsável pelo escalonamento Round-Robin (RR) e pela impressão
     * dos dados sobre o mesmo.
     *
     * @param processos Lista de processos
     */
    public String escalonamento(ArrayList<Processo> processos) {

        tempoDecorrido = 0;
        tempoMedioExecucao = 0;
        trocaContexto = 0;
        tempoMedioEspera = 0;
        quantum = 0;
        quantProcessos = processos.size();
        processoExecucao = null;
        listaProcesso = new LinkedList<>();
        listaProcessoDados = new LinkedList<>();
        dadosProcessamento = new StringBuilder("RR: \ntempo ");

        /*
         * Enumera os processos de P1 a PN, onde N
         * é o número total de processos.
         */
        for (int i = 1; i <= quantProcessos; i++) {
            dadosProcessamento.append(String.format("P%2s%2s", String.valueOf(i), ""));
        }
        // Quebra de linha
        dadosProcessamento.append("\n");

        /*
         * Loop principal, responsável por processar 
         * todos os dados até a obtenção das informações
         * necessária para apresentar os dados sobre os processos.
         */
        while (listaProcessoDados.size() != processos.size()) {

            /*
             * Se o processo entra no instante X, então
             * ele será adicionado a fila de processos
             * para execução
             */
            for (Processo processo : processos) {
                if (processo.getDataCriacao() == tempoDecorrido) {
                    listaProcesso.add(processo);
                }
            }

            /*
             * Se nenhum processo estiver em execução, 
             * irá ser colocado o topo da fila de processos.
             */
            novoProcessoExecucao();

            // Imprime o tempo passando no decorrer do escalonamento
            dadosProcessamento.append(String.format("%2s-%2s ",
                    String.valueOf(tempoDecorrido), (tempoDecorrido + 1)));

            for (int i = 0; i < quantProcessos; i++) {

                if (!listaProcesso.contains(processos.get(i))) {
                    dadosProcessamento.append(String.format("%2s%3s", " ", ""));
                } else {

                    if (processos.get(i).equals(processoExecucao)) {
                        dadosProcessamento.append(String.format("%2s%3s", "#", ""));
                    } else {
                        dadosProcessamento.append(String.format("%2s%3s", "-", ""));
                    }
                }
            }
            // Quebra de linha
            dadosProcessamento.append("\n");

            /*
             * Verifica em qual situção o processo se encaixa,
             * com relação a quanto tempo resta de execução,
             * se já morreu ou se acabou seu quantum e deve ir ao
             * final da lista.
             */
            if (processoExecucao != null && quantum == 0) {
                listaProcesso.peek().setStatus(0);
                if (processoExecucao.getDuracaoRestante() == 0) {
                    processoExecucao = null;
                    listaProcesso.peek().setTermino(tempoDecorrido + 1);
                    listaProcessoDados.add(listaProcesso.poll());

                } else {
                    listaProcesso.add(listaProcesso.poll());
                }
            } else if (processoExecucao != null && processoExecucao.getDuracaoRestante() > 0) {
                processoExecucao.tempo();
            } else if (processoExecucao != null && processoExecucao.getDuracaoRestante() == 0) {
                processoExecucao = null;
                listaProcesso.peek().setTermino(tempoDecorrido + 1);
                listaProcessoDados.add(listaProcesso.poll());

            }
            // Contabiliza o tempo
            quantum--;
            tempoDecorrido += 1;
        }

        /*
         * Calcula o somatório dos tempos gastos na
         * execução e espera pelos processos.
         */
        for (Processo processo : listaProcessoDados) {
            tempoMedioExecucao += processo.getTermino() - processo.getDataCriacao();
            tempoMedioEspera += processo.getTermino() - processo.getDuracao() - processo.getDataCriacao();
        }

        /*
         * Calcula os tempos médios de execução e espera de acordo
         * com a quantidade de processos.
         */
        if (listaProcessoDados.size() > 0) {
            tempoMedioExecucao = tempoMedioExecucao / quantProcessos;
            tempoMedioEspera = tempoMedioEspera / quantProcessos;

            dadosProcessamento.append(String.format("\nTempo médio execução: %.2fs", tempoMedioExecucao));
            dadosProcessamento.append(String.format("\nTempo médio de espera: %.2fs", tempoMedioEspera));
            dadosProcessamento.append(String.format("\nNúmero de troca de contexto: %d", trocaContexto));
        }

        return dadosProcessamento.toString();
    }
}